#!/usr/bin/python2.3

#
# mp3info.py - Duane Maxwell
#
# based on mp3info by Cedric Tefft <cedric@earthling.net>
#
# This work is released under the GNU GPL, version 2 or later.
#
import struct
import os
import sys

layer_tab = [0,3,2,1]

frequencies = [
	[22050,24000,16000,50000],
	[44100,48000,32000,50000],
	[11025,12000,8000,50000]]
	
bitrates = [
	[ # MPEG 2.0
		[0,32,48,56,64,80,96,112,128,144,160,176,192,224,256,None],	# layer 1
		[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,None],		# layer 2
		[0,8,16,24,32,40,48,56,64,80,96,112,128,144,160,None]		# layer 3
	],
	[ # MPEG 1.0
		[0,32,64,96,128,160,192,224,256,288,320,352,384,416,448,None],	# layer 1
		[0,32,48,56,64,80,96,112,128,160,192,224,256,320,384,None],		# layer 2
		[0,32,40,48,56,64,80,96,112,128,160,192,224,256,320,None]		# layer 3
	]]

frame_size_index = [24000,72000,72000]

mode_text = ['stereo','joint stereo','dual channel','mono']

emphasis_text = ['none','50/15 microsecs','reserved','CCITT J 17']

class MP3Header:
	pass

class MP3Info:
	def __init__(self,fd,scanFull=False):
		#fd = open(filePath,"rb")
		self.badFrames = 0
		self.scan(fd,scanFull)
		self.scanID3v1(fd)
		self.scanID3v2(fd)
	
	def scan(self,fd,scanFull,num_samples = 4):
		fd.seek(0,2)
		self.size = fd.tell()
		self.isValid = False
		self.vbr = False
		if not scanFull:
			header = self.get_first_header(fd)
			if header:
				data_start = fd.tell()
				last_rate = 15-header.bitrate
				counter = 0
				while counter<num_samples and last_rate:
					sample_pos = (counter*self.size/num_samples+1)+data_start
					header = self.get_first_header(fd,sample_pos)
					if header:
						bitrate = 15-header.bitrate
					else:
						bitrate = -1
					if bitrate!=last_rate:
						self.vbr = True
						counter = num_samples
						scanFull = True
					last_rate = bitrate
					counter = counter+1
				if not scanFull:
					frame_length = header.frameLength
					self.version = self.header_version(header)
					self.layer = self.header_layer(header)
					self.frames = self.size/frame_length
					self.totalTime = int(float(frame_length*self.frames)/float(self.header_bitrate(header)*125)*1000+0.5)
					self.bitrate = self.header_bitrate(header)*1000
					self.samplerate = self.header_frequency(header)
					self.emphasis = self.header_emphasis(header)
					self.mode = self.header_mode(header)
		if scanFull:
			frame_type = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
			frames = 0
			header = self.get_first_header(fd)
			if header:
				data_start = fd.tell()
				while True:
					bitrate = self.get_next_header(fd)
					if bitrate:
						frame_type[15-bitrate] = frame_type[15-bitrate]+1
						frames = frames+1
					else: break
				counter = 0
				frame_types = 0
				frames_so_far = 0
				totalTime = 0
				total_rate = 0
				vbr_median = -1
				while counter<15:
					if frame_type[counter]:
						frame_types = frame_types+1
						header.bitrate = counter
						frames_so_far = frames_so_far+frame_type[counter]
						totalTime = totalTime+float(self.frameLength(header)*frame_type[counter])/(self.header_bitrate(header)*125)
						total_rate = total_rate+self.header_bitrate(header)*frame_type[counter]
						if vbr_median==-1 and frames_so_far >= frames/2:
							vbr_median = counter
					counter = counter+1
				self.version = self.header_version(header)
				self.layer = self.header_layer(header)
				self.totalTime = int(totalTime*1000+0.5)
				header.bitrate = vbr_median
				self.bitrate = self.header_bitrate(header)*1000
				self.samplerate = self.header_frequency(header)
				self.vbr_average = float(total_rate*1000)/frames
				self.frames = frames
				if frame_types>1: self.vbr = True
				self.emphasis = self.header_emphasis(header)
				self.mode = self.header_mode(header)
				
	def get_first_header(self,fd,start=0):
		fd.seek(start)
		while True:
			while True:
				c = fd.read(1)
				if not c: break
				(c,) = struct.unpack('!1B', c)
				if c==255: break
			if c==255:
				fd.seek(fd.tell()-1)
				valid_start = fd.tell()
				header = self.get_header(fd)
				if header:
					fd.seek(header.frameLength-4,1)
					k = 1
					while k<4 and self.size-fd.tell()>4:
						header1 = self.get_header(fd)
						if header1==None: break
						if not self.same_constant(header,header1): break
						fd.seek(header1.frameLength-4,1)
						k = k+1
					if k==4:
						fd.seek(valid_start)
						self.isValid = True
						return header1
				else:
					fd.seek(valid_start+1)
			else:
				return None
	
	def get_next_header(self,fd):
		skip_bytes = 0
		while True:
			while True:
				c = fd.read(1)
				if not c: break
				(c,) = struct.unpack('!1B', c)
				if c==255: break
				if fd.tell()>=self.size: break
				skip_bytes = skip_bytes+1
			if c==255:
				fd.seek(fd.tell()-1)
				header = self.get_header(fd)
				if header:
					if skip_bytes: self.badFrames = self.badFrames+1
					fd.seek(header.frameLength-4,1)
					return 15-header.bitrate
				else:
					skip_bytes = skip_bytes+4
			else:
				if skip_bytes: self.badFrames = self.badFrames+1
				return 0
	
	def get_header(self,fd):
		header = MP3Header()
		buffer = fd.read(4)
		if not buffer or len(buffer)<4:
			return None
		buffer = struct.unpack('!4B',buffer)
		header.sync = (buffer[0]<<4) | ((buffer[1]&0xE0)>>4)
		if header.sync!=0xFFE:
			return None
		if buffer[1]&0x10:
			header.version = (buffer[1]>>3) & 1
		else:
			header.version = 2
		header.layer = (buffer[1]>>1) & 3
		if not header.layer in [1,2]:
			return None
		header.crc = buffer[1] & 1
		header.bitrate = (buffer[2]>>4) & 0xF
		if header.bitrate == 15:
			return None
		header.frequency = (buffer[2]>>2) & 0x3
		header.padding = (buffer[2]>>1) & 0x1
		header.extension = buffer[2] & 0x1
		header.mode = (buffer[3]>>6) & 0x3
		header.mode_extension = (buffer[3]>>4) & 0x3
		header.copyright = (buffer[3]>>3) & 0x1
		header.original = (buffer[3]>>2) & 0x1
		header.emphasis = buffer[3] & 0x3
		if header.emphasis==2:
			return None
		header.frameLength = self.frameLength(header)
		if header.frameLength>=21: return header
		return None
	
	def frameLength(self,header):
		if header.sync==0xFFE:
			return (frame_size_index[3-header.layer]*((header.version&1)+1)* \
					self.header_bitrate(header)/self.header_frequency(header))+ \
					header.padding
		return 1
	
	def header_version(self,header):
		return 2-header.version

	def header_layer(self,header):
		return layer_tab[header.layer]
	
	def header_bitrate(self,header):
		return bitrates[header.version & 1][3-header.layer][header.bitrate]
	
	def header_frequency(self,header):
		return frequencies[header.version][header.frequency]

	def header_emphasis(self,header):
		return emphasis_text[header.emphasis]

	def header_mode(self,header):
		return mode_text[header.mode]

	def same_constant(self,h1,h2):
		if h1.version != h2.version: return False
		if h1.layer != h2.layer: return False
		if h1.crc != h2.crc: return False
		if h1.frequency != h2.frequency: return False
		if h1.mode != h2.mode: return False
		if h1.copyright != h2.copyright: return False
		if h1.original != h2.original: return False
		if h1.emphasis != h2.emphasis: return False
		return True

	def scanID3v1(self,fd):
		try:
			fd.seek(-128, 2)
		except IOError:
			pass

		data = fd.read(128)
		if data[0:3] != 'TAG':
			self.id3v1 = False
			return
		self.id3v1 = True
		self.id3v1_major_version = 0
		if data[125]=='\0':
			self.id3v1_major_version = 1
	
	def scanID3v2(self,fd):
		fd.seek(0)
		if fd.read(3) != "ID3":
			self.id3v2 = False
			return
		self.id3v2 = True
		(self.id3v2_major_version, self.id3v2_minor_version) = struct.unpack('!2b', fd.read(2))

if __name__=='__main__':
	import sys
	filePath = sys.argv[1]
	i = MP3Info(open(filePath,"rb"))
	print vars(i)
